﻿using System;
using System.Text;

namespace Arex
{
    public static class Integer1
    {
        /// <summary>
        /// Initialization of rectangular jagged array of integer. All elements are zero by default.
        /// </summary>
        /// <param name="rows">Number of rows in result array.</param>
        /// <param name="cols">Number of columns in result array.</param>
        /// <returns>Rectangular jagged array of integer.</returns>
        private static int[][] _init(int rows, int cols)
        {
            int[][] z = new int[rows][];
            for (int i = 0; i < rows; i++)
                z[i] = new int[cols];
            return z;
        }

        /// <summary>
        /// Checks if array size equals to zero.
        /// </summary>
        /// <param name="a">Array of integer.</param>
        /// <returns>True if array size equals zero, false otherwise.</returns>
        private static bool _has_zero_size(int[] a)
        {
            return a.Length == 0;
        }

        /// <summary>
        /// Check if two arrays have same size.
        /// </summary>
        /// <param name="a">First array of integer.</param>
        /// <param name="b">Second array of integer.</param>
        /// <returns>True if array sizes are different, false otherwise.</returns>
        private static bool _have_diff_size(int[] a, int[] b)
        {
            return a.Length != b.Length;
        }

        #region Queries

        /// <summary>
        /// Checks if all elements are equal to zero (i.e. when the array is initialized with zeros).
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>True if all elements are equal to zero.</returns>
        public static bool IsZero(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            for (int i = 0; i < a.Length; i++)
                if (a[i] != 0) return false;
            return true;
        }

        #endregion

        #region Unary operations

        /// <summary>
        /// Negation of all elements in array.
        /// Example: if a = [1, 2, 3] then b = a.Negate() returns [-1, -2, -3]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Array of all elements with opposite sign.</returns>
        public static int[] Negate(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = -a[i];
            return z;
        }

        #endregion

        #region Element by element operations

        /// <summary>
        /// Scalar element-wise add operation.
        /// Example: if a = [1, 2, 3] and b = 1 then c = a.Add(b) returns [2, 3, 4]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Integer value to add.</param>
        /// <returns>Array of all elements with values equal to sum of element and integer value.</returns>
        public static int[] Add(this int[] a, int b)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = a[i] + b;
            return z;
        }

        /// <summary>
        /// Scalar element-wise add operation.
        /// Example: if a = [1, 2, 3] and b = [3, 4, 5] then c = a.Add(b) returns [4, 6, 8]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Integer value array to add.</param>
        /// <returns>Array of all elements with values equal to sum of correspondent elements.</returns>
        public static int[] Add(this int[] a, int[] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (_have_diff_size(a, b)) throw new ArgumentOutOfRangeException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = a[i] + b[i];
            return z;
        }

        /// <summary>
        /// Scalar element-wise subtract operation.
        /// Example: if a = [1, 2, 3] and b = 1 then c = a.Sub(b) returns [0, 1, 2]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Integer value to subtract.</param>
        /// <returns>Array of all elements with values equal to subtraction of element and integer value.</returns>
        public static int[] Sub(this int[] a, int b)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = a[i] - b;
            return z;
        }

        /// <summary>
        /// Scalar element-wise subtract operation.
        /// Example: if a = [1, 2, 3] and b = [3, 4, 5] then c = a.Sub(b) returns [-2, -2, -2]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Integer value array to subtract.</param>
        /// <returns>Array of all elements with values equal to subtraction of correspondent elements.</returns>
        public static int[] Sub(this int[] a, int[] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (_have_diff_size(a, b)) throw new ArgumentOutOfRangeException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = a[i] - b[i];
            return z;
        }

        /// <summary>
        /// Scalar element-wise multiply operation.
        /// Example: if a = [1, 2, 3] and b = 1 then c = a.Mul(b) returns [1, 2, 3]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Integer value to multiply.</param>
        /// <returns>Array of all elements with values equal to multiplication of element and integer value.</returns>
        public static int[] Mul(this int[] a, int b)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = a[i] * b;
            return z;
        }

        /// <summary>
        /// Scalar element-wise add operation.
        /// Example: if a = [1, 2, 3] and b = [3, 4, 5] then c = a.Mul(b) returns [3, 8, 15]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Integer value array to multiply.</param>
        /// <returns>Array of all elements with values equal to multiplication of correspondent elements.</returns>
        public static int[] Mul(this int[] a, int[] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (_have_diff_size(a, b)) throw new ArgumentOutOfRangeException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = a[i] * b[i];
            return z;
        }

        /// <summary>
        /// Scalar element-wise divide operation.
        /// Example: if a = [1, 2, 3] and b = 1 then c = a.Div(b) returns [1, 2, 3]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Integer value to divide.</param>
        /// <returns>Array of all elements with values equal to division of element and integer value.</returns>
        public static int[] Div(this int[] a, int b)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();
            if (b == 0) throw new DivideByZeroException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = a[i] / b;
            return z;
        }

        /// <summary>
        /// Scalar element-wise divide operation.
        /// Example: if a = [1, 2, 3] and b = [1, 4, 3] then c = a.Div(b) returns [1, 0, 1]
        /// Example: if a = [1, 2, 3] and b = [1, 0, 1] then c = a.Div(b) throwns DivideByZeroException.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="b">Integer value array to divide.</param>
        /// <returns>Array of all elements with values equal to division of correspondent elements.</returns>
        public static int[] Div(this int[] a, int[] b)
        {
            if (a == null || b == null) throw new ArgumentNullException();
            if (_has_zero_size(a) || _has_zero_size(b)) throw new ArrayZeroSizeException();
            if (_have_diff_size(a, b)) throw new ArgumentOutOfRangeException();

            int[] z = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = a[i] / b[i];
            return z;
        }

        #endregion

        #region Conversion operations

        /// <summary>
        /// String representation of array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>String representation of array with default format.</returns>
        public static string ToText(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            StringBuilder z = new StringBuilder();
            for (int i = 0; i < a.Length; i++)
                z.Append(a[i]).Append(' ');
            return z.ToString();
        }

        /// <summary>
        /// Conversion of int type array into long type array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Long type array of elements equal to corresponding source array.</returns>
        public static long[] ToLong(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            long[] z = new long[a.Length];
            for (int i = 0; i < a.Length; i++)
                z[i] = Convert.ToInt64(a[i]);
            return z;
        }

        /// <summary>
        /// Reshape of one dimensional array to two dimensional array.
        /// Example: if a = [1, 2, 3] then b = a.Reshape(2, 2) returns [[1, 2], [3, 0]]
        /// Example: if a = [1, 2, 3] then b = a.Reshape(3, 3) returns [[1, 2, 3], [0, 0, 0], [0, 0, 0]]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="rows">Number of rows in result array.</param>
        /// <param name="cols">Number of columns in result array.</param>
        /// <returns>Rectangular jagged two dimensional array of integer.</returns>
        public static int[][] Reshape(this int[] a, int rows, int cols)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();
            if (rows <= 0 || cols <= 0) throw new ArgumentOutOfRangeException();

            int[][] z = _init(rows, cols);
            int nth = 0;
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    z[i][j] = nth < a.Length ? a[nth] : 0;
                    nth++;
                }
            }

            return z;
        }

        #endregion

        #region Copying

        /// <summary>
        /// Deep copy of array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Deep copy of source array.</returns>
        public static int[] DeepCopy(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            int[] z = new int[a.Length];
            Buffer.BlockCopy(a, 0, z, 0, a.Length);

            return z;
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Initialize array with all elements equal to value.
        /// This method doesn't return value but performs inplace operation.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="val">Value to initialize array.</param>
        public static void ByValue(this int[] a, int val)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            for (int i = 0; i < a.Length; i++)
                a[i] = val;
        }

        /// <summary>
        /// Initialize array with all elements equal to random value.
        /// This method doesn't return value but performs inplace operation.
        /// </summary>
        /// <param name="a">Extended array.</param>
        public static void ByRandom(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            Random r = new Random();
            for (int i = 0; i < a.Length; i++)
                a[i] = r.Next();
        }

        /// <summary>
        /// Initialize array with values starting from value with defined step.
        /// This method doesn't return value but performs inplace operation.
        /// Example: if a = [0, 0, 0] then a.ByRange(2, 2) sets a = [2, 4, 6]
        /// Example: if a = [0, 0, 0, 0, 0] then a.ByRange(2, -1) sets a = [2, 1, 0, -1, -2]
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <param name="from">First element value.</param>
        /// <param name="step">Step value.</param>
        public static void ByRange(this int[] a, int from, int step)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            for (int i = 0; i < a.Length; i++)
                a[i] = from + i * step;
        }

        #endregion

        #region Math operations

        /// <summary>
        /// Sum of all elements in array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Sum of all elements in array.</returns>
        public static int Sum(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            int z = 0;
            for (int i = 0; i < a.Length; i++)
                z += a[i];
            return z;
        }

        /// <summary>
        /// Value of maximum element in array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Value of maximum element in array.</returns>
        public static int Max(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            int z = a[0];
            for (int i = 0; i < a.Length; i++)
                z = a[i] > z ? a[i] : z;
            return z;
        }

        /// <summary>
        /// Value of minimum element in array.
        /// </summary>
        /// <param name="a">Extended array.</param>
        /// <returns>Value of minimum element in array.</returns>
        public static int Min(this int[] a)
        {
            if (a == null) throw new ArgumentNullException();
            if (_has_zero_size(a)) throw new ArrayZeroSizeException();

            int z = a[0];
            for (int i = 0; i < a.Length; i++)
                z = a[i] < z ? a[i] : z;
            return z;
        }

        #endregion
    }
}
